Vue3.2 + Element

您所在的位置:网站首页 el-table-column 宽度 Vue3.2 + Element

Vue3.2 + Element

2023-04-22 02:55| 来源: 网络整理| 查看: 265

前言 📖

在公司经常接触到后台管理系统的开发,基本上 90% 以上都是 table 页面,业务逻辑也基本上一样。刚开始也想的是找一个基于 element 二次封装的 el-table,在搜寻过程中也接触了很多优秀的项目,其中包括:(vxe-table、avue……)但是这些项目终归没有自己开发灵活,所有就诞生了我的 Pro-Table 组件🎉🎉🎉 目前能节省我 60% 的工作量。(仅代表个人实践)

💢 请注意:以下内容只代表我个人封装思想,如果你觉得还不错,请帮我点个小小的 star,如果你有更好的想法,请在评论区留言,蟹蟹 😆😆

一、在线预览 👀 Link:admin.spicyboy.cn 二、Git 仓库地址 (欢迎 Star⭐⭐⭐) Gitee:gitee.com/HalseySpicy… GitHub:github.com/HalseySpicy… 三、Pro-Table 功能 🔨🔨🔨 表格内容自适应屏幕宽高,溢出内容表格内部滚动。 表格数据操作 Hooks (单条数据删除、批量删除、重置密码、状态切换……) 表格数据多选 Hooks (支持现跨页勾选数据) 表格序号、每行可自定义展开信息、表格头部自定义渲染(使用 tsx 语法) 表格列排序、单元格内容格式化(有字典会根据字典自动格式化) 树形表格展示(后期会增加懒加载) 表格数据导入组件、导出钩子函数 表格查询(可携带初始参数)、重置功能的封装 表格分页模块封装(Pagination) 表格数据刷新、列显隐、搜索显隐设置 四、需求分析 📑 首先我们来看效果图(总共可以分为五个区域):

table1.png

1、表格搜索区域 2、表格数据操作按钮区域 3、表格功能按钮区域 4、表格主体内容展示区域 5、表格分页区域 1、搜索区域分析:

可以看到搜索区域的字段都是存在于表格当中的,并且每个页面的搜索、重置方法都是一样的逻辑,只是不同的查询参数而已。我们完全可以在传表格配置项 columns 时,直接指定某个字段的 search:true 就能把该项变为搜索项,然后使用 SearchType 字段可以指定搜索框的类型,最后把表格的搜索方法都封装成 Hooks 钩子函数。页面上完全就不会存在搜索逻辑了。

2、表格数据操作按钮区域分析:

表格数据操作按钮基本上每个页面都会不一样,所以我们直接使用 作用域插槽 来完成每个页面的数据操作按钮区域,作用域插槽 可以将表格多选数据信息从 Pro-Table 的 Hooks 多选钩子函数中传到页面上使用。

3、表格功能按钮区域分析:

这块区域没什么特殊功能,只有三个按钮,其功能分别为:表格数据刷新(一直会携带当前查询和分页条件)、表格列显隐设置、表格搜索区域显隐(方便展示更多的数据信息)。 可通过 toolButton 属性控制这块区域的显隐。

4、表格主体内容展示区域分析:

这块区域主要就是数据展示,配置 columns 项传到 Pro-Table 组件中就行了。使用作用域插槽可以自定义每一列的显示自己需要的内容,还支持表格数据多选(内部已封装了多选 Hooks 钩子函数)。

5、表格分页区域分析:

分页也没有什么特殊的功能,该支持的都支持了。 🤣🤣

五、Pro-Table 文档 1、Pro-Table 属性配置: 字段字段类型是否必传默认值字段描述columnsColumnProps✅—Pro-Table 会根据此字段渲染搜索表单与表格列requestApiFunction✅—获取表格数据的请求 APIdataCallbackFunction❌—返回数据的回调函数,可以对数据进行处理paginationBoolean❌true是否显示分页组件initParamObject❌{}是否携带表格请求的初始化参数borderBoolean❌true是否带有纵向边框stripeBoolean❌false是否为斑马纹 tabletoolButtonBoolean❌true是否显示表格工具按钮区域childrenNameString❌children当表格为树形表格时,指定 children 字段名 2、ColumnProps 属性配置(都是可选参数): 字段字段类型默认值可选值字段描述typeString—index | selection | expand特殊类型(序号、多选、展开)propString——字段名称对应列内容的字段名labelString——表头标题widthNumber | String——单元格宽度minWidthNumber | String——单元格最小列宽isShowBooleantrue—是否显示在表格内sortableBooleanfalse—是否静态排序fixedString—left | right固定在表格左、右tagBooleanfalse—是否是标签展示searchBooleanfalse—是否为搜索项searchTypeStringtexttext | select | multipleSelect | treeSelect | mutipleTreeSelect | date | timerange | datetimerange搜索项类型searchPropsObject——搜索项参数,根据 element 文档来,标签自带属性 > props 属性searchInitParamString | Number | Boolean | Any[]——搜索项是否带初始化参数enumObject——字典,可格式化单元格,还可以作为搜索框的下拉选项renderHeaderFunction——自定义表头 六、代码实现💪(详情去项目里查看,这里只贴了一部分代码) 使用一段话总结下我的想法:📚📚

把一个表格页面所有重复的功能 (表格多选、查询、重置、刷新、分页器、数据操作二次确认、文件下载、文件上传) 都封装成 Hooks 函数钩子,然后在 Pro-Table 组件中使用这些函数钩子。在页面中使用的时,只需传给 Pro-Table 当前表格数据的请求 API,表格配置项 columns 就行了,数据传输都使用作用域插槽从 Pro-Table 传给父组件就能在页面上获取到了。

1、常用 Hooks 函数 useTable: import { Table } from "./interface"; import { reactive, computed, onMounted, toRefs } from "vue"; /** * @description table 页面操作方法封装 * @param {Function} api 获取表格数据 api 方法(必传) * @param {Object} initParam 获取数据初始化参数(非必传,默认为{}) * @param {Boolean} isPageable 是否有分页(非必传,默认为true) * @param {Function} dataCallBack 对后台返回的数据进行处理的方法(非必传) * */ export const useTable = ( api: (params: any) => Promise, initParam: object = {}, isPageable: boolean = true, dataCallBack?: (data: any) => any ) => { const state = reactive({ // 表格数据 tableData: [], // 分页数据 pageable: { // 当前页数 pageNum: 1, // 每页显示条数 pageSize: 10, // 总条数 total: 0 }, // 查询参数(只包括查询) searchParam: {}, // 初始化默认的查询参数 searchInitParam: {}, // 总参数(包含分页和查询参数) totalParam: {} }); /** * @description 分页查询参数(只包括分页和表格字段排序,其他排序方式可自行配置) * */ const pageParam = computed({ get: () => { return { pageNum: state.pageable.pageNum, pageSize: state.pageable.pageSize }; }, set: (newVal: any) => { console.log("我是分页更新之后的值", newVal); } }); // 初始化的时候需要做的事情就是 设置表单查询默认值 && 获取表格数据(reset函数的作用刚好是这两个功能) onMounted(() => { reset(); }); /** * @description 获取表格数据 * @return void * */ const getTableList = async () => { try { // 先把初始化参数和分页参数放到总参数里面 Object.assign(state.totalParam, initParam, isPageable ? pageParam.value : {}); let { data } = await api(state.totalParam); dataCallBack && (data = dataCallBack(data)); state.tableData = isPageable ? data.datalist : data; // 解构后台返回的分页数据 (如果有分页更新分页信息) const { pageNum, pageSize, total } = data; isPageable && updatePageable({ pageNum, pageSize, total }); } catch (error) { console.log(error); } }; /** * @description 更新查询参数 * @return void * */ const updatedTotalParam = () => { state.totalParam = {}; // 处理查询参数,可以给查询参数加自定义前缀操作 let nowSearchParam: { [key: string]: any } = {}; // 防止手动清空输入框携带参数(这里可以自定义查询参数前缀) for (let key in state.searchParam) { // * 某些情况下参数为 false/0 也应该携带参数 if (state.searchParam[key] || state.searchParam[key] === false || state.searchParam[key] === 0) { nowSearchParam[key] = state.searchParam[key]; } } Object.assign(state.totalParam, nowSearchParam, isPageable ? pageParam.value : {}); }; /** * @description 更新分页信息 * @param {Object} resPageable 后台返回的分页数据 * @return void * */ const updatePageable = (resPageable: Table.Pageable) => { Object.assign(state.pageable, resPageable); }; /** * @description 表格数据查询 * @return void * */ const search = () => { state.pageable.pageNum = 1; updatedTotalParam(); getTableList(); }; /** * @description 表格数据重置 * @return void * */ const reset = () => { state.pageable.pageNum = 1; state.searchParam = {}; // 重置搜索表单的时,如果有默认搜索参数,则重置默认的搜索参数 Object.keys(state.searchInitParam).forEach(key => { state.searchParam[key] = state.searchInitParam[key]; }); updatedTotalParam(); getTableList(); }; /** * @description 每页条数改变 * @param {Number} val 当前条数 * @return void * */ const handleSizeChange = (val: number) => { state.pageable.pageNum = 1; state.pageable.pageSize = val; getTableList(); }; /** * @description 当前页改变 * @param {Number} val 当前页 * @return void * */ const handleCurrentChange = (val: number) => { state.pageable.pageNum = val; getTableList(); }; return { ...toRefs(state), getTableList, search, reset, handleSizeChange, handleCurrentChange }; }; 复制代码 useSelection: import { ref, computed } from "vue"; /** * @description 表格多选数据操作 * */ export const useSelection = () => { // 是否选中数据 const isSelected = ref(false); // 选中的数据列表 const selectedList = ref([]); // 当前选中的所有ids(数组),可根据项目自行配置id字段 const selectedListIds = computed((): string[] => { let ids: string[] = []; selectedList.value.forEach(item => { ids.push(item["id"]); }); return ids; }); // 获取行数据的 Key,用来优化 Table 的渲染;在使用跨页多选时,该属性是必填的 const getRowKeys = (row: { id: string }) => { return row.id; }; /** * @description 多选操作 * @param {Array} rowArr 当前选择的所有数据 * @return void */ const selectionChange = (rowArr: any) => { rowArr.length === 0 ? (isSelected.value = false) : (isSelected.value = true); selectedList.value = rowArr; }; return { isSelected, selectedList, selectedListIds, selectionChange, getRowKeys }; }; 复制代码 useDownload: import { ElNotification } from "element-plus"; /** * @description 接收数据流生成blob,创建链接,下载文件 * @param {Function} api 导出表格的api方法(必传) * @param {String} tempName 导出的文件名(必传) * @param {Object} params 导出的参数(默认为空对象) * @param {Boolean} isNotify 是否有导出消息提示(默认为 true) * @param {String} fileType 导出的文件格式(默认为.xlsx) * @return void * */ export const useDownload = async ( api: (param: any) => Promise, tempName: string, params: any = {}, isNotify: boolean = true, fileType: string = ".xlsx" ) => { if (isNotify) { ElNotification({ title: "温馨提示", message: "如果数据庞大会导致下载缓慢哦,请您耐心等待!", type: "info", duration: 3000 }); } try { const res = await api(params); // 这个地方的type,经测试不传也没事,因为zip文件不知道type是什么 // const blob = new Blob([res], { // type: "application/vnd.ms-excel;" // }); const blob = new Blob([res]); // 兼容edge不支持createObjectURL方法 if ("msSaveOrOpenBlob" in navigator) return window.navigator.msSaveOrOpenBlob(blob, tempName + fileType); const blobUrl = window.URL.createObjectURL(blob); const exportFile = document.createElement("a"); exportFile.style.display = "none"; exportFile.download = `${tempName}${fileType}`; exportFile.href = blobUrl; document.body.appendChild(exportFile); exportFile.click(); // 去除下载对url的影响 document.body.removeChild(exportFile); window.URL.revokeObjectURL(blobUrl); } catch (error) { console.log(error); } }; 复制代码 useHandleData: import { ElMessageBox, ElMessage } from "element-plus"; import { HandleData } from "./interface"; /** * @description 操作单条数据信息(二次确认【删除、禁用、启用、重置密码】) * @param {Function} api 操作数据接口的api方法(必传) * @param {Object} params 携带的操作数据参数 {id,params}(必传) * @param {String} message 提示信息(必传) * @param {String} confirmType icon类型(不必传,默认为 warning) * @return Promise */ export const useHandleData = ( api: (params: any) => Promise, params: Parameters[0], message: string, confirmType: HandleData.MessageType = "warning" ) => { return new Promise((resolve, reject) => { ElMessageBox.confirm(`是否${message}?`, "温馨提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: confirmType, draggable: true }).then(async () => { const res = await api(params); if (!res) return reject(false); ElMessage({ type: "success", message: `${message}成功!` }); resolve(true); }); }); }; 复制代码 2、Pro-table 组件: Template: {{ item.enum?.length ? filterEnum(scope.row[item.prop!], item.enum!, item.searchProps) : formatValue(scope.row[item.prop!]) }} {{ item.enum?.length ? filterEnum(scope.row[item.prop!], item.enum!, item.searchProps) : formatValue(scope.row[item.prop!]) }} 暂无数据 复制代码 Script: import { ref, watch } from "vue"; import { useTable } from "@/hooks/useTable"; import { useSelection } from "@/hooks/useSelection"; import { Refresh, Operation, Search } from "@element-plus/icons-vue"; import { ColumnProps } from "@/components/ProTable/interface"; import { filterEnum, formatValue } from "@/utils/util"; import SearchForm from "@/components/SearchForm/index.vue"; import Pagination from "./components/Pagination.vue"; import ColSetting from "./components/ColSetting.vue"; // 表格 DOM 元素 const tableRef = ref(); // 是否显示搜索模块 const isShowSearch = ref(true); interface ProTableProps { columns: Partial[]; // 列配置项 requestApi: (params: any) => Promise; // 请求表格数据的api ==> 必传 dataCallback?: (data: any) => any; // 返回数据的回调函数,可以对数据进行处理 pagination?: boolean; // 是否需要分页组件 ==> 非必传(默认为true) initParam?: any; // 初始化请求参数 ==> 非必传(默认为{}) border?: boolean; // 表格是否显示边框 ==> 非必传(默认为true) stripe?: boolean; // 是否带斑马纹表格 ==> 非必传(默认为false) toolButton?: boolean; // 是否显示表格功能按钮 ==> 非必传(默认为true) childrenName?: string; // 当数据存在 children 时,指定 children key 名字 ==> 非必传(默认为"children") } // 接受父组件参数,配置默认值 const props = withDefaults(defineProps(), { columns: () => [], pagination: true, initParam: {}, border: true, stripe: false, toolButton: true, childrenName: "children" }); // 表格多选 Hooks const { selectionChange, getRowKeys, selectedListIds, isSelected } = useSelection(); // 表格操作 Hooks const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } = useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback); // 监听页面 initParam 改化,重新获取表格数据 watch( () => props.initParam, () => { getTableList(); }, { deep: true } ); // 表格列配置项处理(添加 isShow 属性,控制显示/隐藏) const tableColumns = ref(); tableColumns.value = props.columns.map(item => { return { ...item, isShow: item.isShow ?? true }; }); // 如果当前 enum 为后台数据需要请求数据,则调用该请求接口,获取enum数据 tableColumns.value.forEach(async item => { if (item.enum && typeof item.enum === "function") { const { data } = await item.enum(); item.enum = data; } }); // 过滤需要搜索的配置项 const searchColumns = tableColumns.value.filter(item => item.search); // 设置搜索表单的默认值 searchColumns.forEach(column => { if (column.searchInitParam !== undefined && column.searchInitParam !== null) { searchInitParam.value[column.prop!] = column.searchInitParam; } }); // * 列设置 const colRef = ref(); // 过滤掉不需要设置显隐的列 const colSetting = tableColumns.value.filter((item: Partial) => { return ( item.type !== "selection" && item.type !== "index" && item.type !== "expand" && item.prop !== "operation" && item.isShow !== false ); }); const openColSetting = () => { colRef.value.openColSetting(); }; // 暴露给父组件的参数和方法 defineExpose({ searchParam, refresh: getTableList }); 复制代码 3、页面使用: 新增用户 批量添加用户 导出用户数据 批量删除用户 {{ scope.row }} {{ scope.row.status === 1 ? "启用" : "禁用" }} 查看 编辑 重置密码 删除 import { ref, reactive } from "vue"; import { ElMessage } from "element-plus"; import { User } from "@/api/interface"; import { ColumnProps } from "@/components/ProTable/interface"; import { useHandleData } from "@/hooks/useHandleData"; import { useDownload } from "@/hooks/useDownload"; import { useAuthButtons } from "@/hooks/useAuthButtons"; import ProTable from "@/components/ProTable/index.vue"; import ImportExcel from "@/components/ImportExcel/index.vue"; import UserDrawer from "@/views/proTable/components/UserDrawer.vue"; import { CirclePlus, Delete, EditPen, Download, Upload, View, Refresh } from "@element-plus/icons-vue"; import { getUserList, deleteUser, editUser, addUser, changeUserStatus, resetUserPassWord, exportUserInfo, BatchAddUser, getUserStatus, getUserGender } from "@/api/modules/user"; // 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) const proTable = ref(); // 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据) const initParam = reactive({ type: 1 }); // dataCallback 是对于返回的表格数据做处理,如果你后台返回的数据不是 datalist && total && pageNum && pageSize 这些字段,那么你可以在这里进行处理成这些字段 const dataCallback = (data: any) => { return { datalist: data.datalist, total: data.total, pageNum: data.pageNum, pageSize: data.pageSize }; }; // 页面按钮权限 const { BUTTONS } = useAuthButtons(); // 自定义渲染头部(使用tsx语法) const renderHeader = (scope: any) => { return ( { ElMessage.success("我是自定义表头"); }} > {scope.row.label} ); }; // 表格配置项 const columns: Partial[] = [ { type: "selection", width: 80, fixed: "left" }, { type: "index", label: "#", width: 80 }, { type: "expand", label: "Expand", width: 100 }, { prop: "username", label: "用户姓名", width: 130, search: true, searchProps: { disabled: true }, renderHeader }, // 😄 enum 可以直接是数组对象,也可以是请求方法(proTable 内部会执行获取 enum 的这个方法),下面用户状态也同理 // 😄 enum 为请求方法时,后台返回的数组对象 key 值不是 label 和 value 的情况,可以在 searchProps 中指定 label 和 value 的 key 值 { prop: "gender", label: "性别", width: 120, sortable: true, search: true, searchType: "select", enum: getUserGender, searchProps: { label: "genderLabel", value: "genderValue" } }, { prop: "idCard", label: "身份证号", search: true }, { prop: "email", label: "邮箱", search: true }, { prop: "address", label: "居住地址", search: true }, { prop: "status", label: "用户状态", sortable: true, search: true, searchType: "select", enum: getUserStatus, searchProps: { label: "userLabel", value: "userStatus" } }, { prop: "createTime", label: "创建时间", width: 200, sortable: true, search: true, searchType: "datetimerange", searchProps: { disabledDate: (time: Date) => time.getTime() < Date.now() - 8.64e7 }, searchInitParam: ["2022-08-30 00:00:00", "2022-08-20 23:59:59"] }, { prop: "operation", label: "操作", width: 330, fixed: "right", renderHeader } ]; // 删除用户信息 const deleteAccount = async (params: User.ResUserList) => { await useHandleData(deleteUser, { id: [params.id] }, `删除【${params.username}】用户`); proTable.value.refresh(); }; // 批量删除用户信息 const batchDelete = async (id: string[]) => { await useHandleData(deleteUser, { id }, "删除所选用户信息"); proTable.value.refresh(); }; // 重置用户密码 const resetPass = async (params: User.ResUserList) => { await useHandleData(resetUserPassWord, { id: params.id }, `重置【${params.username}】用户密码`); proTable.value.refresh(); }; // 切换用户状态 const changeStatus = async (row: User.ResUserList) => { await useHandleData(changeUserStatus, { id: row.id, status: row.status == 1 ? 0 : 1 }, `切换【${row.username}】用户状态`); proTable.value.refresh(); }; // 导出用户列表 const downloadFile = async () => { useDownload(exportUserInfo, "用户列表", proTable.value.searchParam); }; // 批量添加用户 interface DialogExpose { acceptParams: (params: any) => void; } const dialogRef = ref(); const batchAdd = () => { let params = { title: "用户", tempApi: exportUserInfo, importApi: BatchAddUser, getTableList: proTable.value.refresh }; dialogRef.value!.acceptParams(params); }; // 打开 drawer(新增、查看、编辑) interface DrawerExpose { acceptParams: (params: any) => void; } const drawerRef = ref(); const openDrawer = (title: string, rowData: Partial = { avatar: "" }) => { let params = { title, rowData: { ...rowData }, isView: title === "查看", apiUrl: title === "新增" ? addUser : title === "编辑" ? editUser : "", getTableList: proTable.value.refresh }; drawerRef.value!.acceptParams(params); }; 复制代码


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3